/*
 * Copyright 1999-2101 Alibaba Group.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.fastjson.parser;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;

import com.alibaba.fastjson.JSONException;

/**
 * 
 * @author wenshao<szujobs@hotmail.com>
 * 
 */
public class TypeUtils {
	public static final String castToString(Object value) {
		if (value == null) {
			return null;
		}

		return value.toString();
	}

	public static final Byte castToByte(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Number) {
			return ((Number) value).byteValue();
		}

		if (value instanceof String) {
			return Byte.parseByte((String) value);
		}

		throw new JSONException("can not cast to byte, value : " + value);
	}

	public static final Short castToShort(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Number) {
			return ((Number) value).shortValue();
		}

		if (value instanceof String) {
			return Short.parseShort((String) value);
		}

		throw new JSONException("can not cast to short, value : " + value);
	}

	public static final BigDecimal castToBigDecimal(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof BigDecimal) {
			return (BigDecimal) value;
		}

		if (value instanceof BigInteger) {
			return new BigDecimal((BigInteger) value);
		}

		return new BigDecimal(value.toString());
	}

	public static final BigInteger castToBigInteger(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof BigInteger) {
			return (BigInteger) value;
		}

		return new BigInteger(value.toString());
	}

	public static final Float castToFloat(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Number) {
			return ((Number) value).floatValue();
		}

		if (value instanceof String) {
			return Float.parseFloat(value.toString());
		}

		throw new JSONException("can not cast to float, value : " + value);
	}

	public static final Double castToDouble(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Number) {
			return ((Number) value).doubleValue();
		}

		if (value instanceof String) {
			return Double.parseDouble(value.toString());
		}

		throw new JSONException("can not cast to double, value : " + value);
	}

	public static final Date castToDate(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Calendar) {
			return ((Calendar) value).getTime();
		}

		if (value instanceof Date) {
			return (Date) value;
		}

		long longValue = 0;

		if (value instanceof Number) {
			longValue = ((Number) value).longValue();
		}

		if (value instanceof String) {
			longValue = Long.parseLong((String) value);
		}

		if (longValue <= 0) {
			throw new JSONException("can not cast to Date, value : " + value);
		}

		return new Date(longValue);
	}

	public static final java.sql.Date castToSqlDate(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Calendar) {
			return new java.sql.Date(((Calendar) value).getTimeInMillis());
		}

		if (value instanceof java.sql.Date) {
			return (java.sql.Date) value;
		}
		
		if (value instanceof java.util.Date) {
			return new java.sql.Date(((java.util.Date) value).getTime());
		}

		long longValue = 0;

		if (value instanceof Number) {
			longValue = ((Number) value).longValue();
		}

		if (value instanceof String) {
			longValue = Long.parseLong((String) value);
		}

		if (longValue <= 0) {
			throw new JSONException("can not cast to Date, value : " + value);
		}

		return new java.sql.Date(longValue);
	}

	public static final java.sql.Timestamp castToTimestamp(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Calendar) {
			return new java.sql.Timestamp(((Calendar) value).getTimeInMillis());
		}

		if (value instanceof java.sql.Timestamp) {
			return (java.sql.Timestamp) value;
		}
		
		if (value instanceof java.util.Date) {
			return new java.sql.Timestamp(((java.util.Date) value).getTime());
		}

		long longValue = 0;

		if (value instanceof Number) {
			longValue = ((Number) value).longValue();
		}

		if (value instanceof String) {
			longValue = Long.parseLong((String) value);
		}

		if (longValue <= 0) {
			throw new JSONException("can not cast to Date, value : " + value);
		}

		return new java.sql.Timestamp(longValue);
	}

	public static final Long castToLong(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Number) {
			return ((Number) value).longValue();
		}

		if (value instanceof String) {
			return Long.parseLong((String) value);
		}

		throw new JSONException("can not cast to long, value : " + value);
	}

	public static final Integer castToInt(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Integer) {
			return (Integer) value;
		}

		if (value instanceof Number) {
			return ((Number) value).intValue();
		}

		if (value instanceof String) {
			return Integer.parseInt((String) value);
		}

		throw new JSONException("can not cast to int, value : " + value);
	}

	public static final Boolean castToBoolean(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Boolean) {
			return (Boolean) value;
		}

		if (value instanceof Number) {
			return ((Number) value).intValue() == 1;
		}

		if (value instanceof String) {
			String str = (String) value;
			if ("true".equals(str)) {
				return Boolean.TRUE;
			}
			if ("false".equals(str)) {
				return Boolean.FALSE;
			}
		}

		throw new JSONException("can not cast to int, value : " + value);
	}

	public static final <T> T castToJavaBean(Object obj, Class<T> clazz) {
		return cast(obj, clazz, JavaBeanMapping.getGlobalInstance());
	}

	@SuppressWarnings("unchecked")
	public static final <T> T cast(Object obj, Class<T> clazz, JavaBeanMapping mapping) {
		if (obj == null) {
			return null;
		}

		if (clazz == obj.getClass()) {
			return (T) obj;
		}

		if (obj instanceof Map) {
			if (clazz == Map.class) {
				return (T) obj;
			}

			return castToJavaBean((Map<String, Object>) obj, clazz, mapping);
		}

		if (clazz.isAssignableFrom(obj.getClass())) {
			return (T) obj;
		}

		if (clazz == boolean.class || clazz == Boolean.class) {
			return (T) castToBoolean(obj);
		}

		if (clazz == byte.class || clazz == Byte.class) {
			return (T) castToByte(obj);
		}

		// if (clazz == char.class || clazz == Character.class) {
		// return (T) castToCharacter(obj);
		// }

		if (clazz == short.class || clazz == Short.class) {
			return (T) castToShort(obj);
		}

		if (clazz == int.class || clazz == Integer.class) {
			return (T) castToInt(obj);
		}

		if (clazz == long.class || clazz == Long.class) {
			return (T) castToLong(obj);
		}
		
		if (clazz == float.class || clazz == Float.class) {
			return (T) castToFloat(obj);
		}
		
		if (clazz == double.class || clazz == Double.class) {
			return (T) castToDouble(obj);
		}

		if (clazz == String.class) {
			return (T) castToString(obj);
		}

		if (clazz == BigDecimal.class) {
			return (T) castToBigDecimal(obj);
		}

		if (clazz == BigInteger.class) {
			return (T) castToBigInteger(obj);
		}

		if (clazz == Date.class) {
			return (T) castToDate(obj);
		}

		if (clazz == java.sql.Date.class) {
			return (T) castToSqlDate(obj);
		}
		
		if (clazz == java.sql.Timestamp.class) {
			return (T) castToTimestamp(obj);
		}

		throw new JSONException("can not cast to : " + clazz.getName());
	}

	@SuppressWarnings("unchecked")
	public static final <T> T cast(Object obj, Type type, JavaBeanMapping mapping) {
		if (type instanceof Class) {
			return (T) cast(obj, (Class<T>) type, mapping);
		}

		throw new JSONException("can not cast to : " + type);
	}

	public static final <T> T castToJavaBean(Map<String, Object> map, Class<T> clazz, JavaBeanMapping mapping) {
		Map<String, Method> setters = mapping.getSetters(clazz);

		try {
			T object = clazz.newInstance();

			for (Map.Entry<String, Method> entry : setters.entrySet()) {
				String key = entry.getKey();
				Method method = entry.getValue();

				Object value = map.get(key);
				value = cast(value, method.getGenericParameterTypes()[0], mapping);
				method.invoke(object, new Object[] { value });
			}

			return object;
		} catch (Exception e) {
			throw new JSONException(e.getMessage(), e);
		}
	}
}
